<?php

namespace app\common\service;

use ba\Exception;

class SnowflakeService extends ApiService
{
    // 配置参数
    private $timestampBits;
    private $datacenterBits;
    private $workerBits;
    private $sequenceBits;

    // 计算得出的常量
    private $maxDatacenterId;
    private $maxWorkerId;
    private $maxSequence;
    private $timestampLeftShift;
    private $datacenterLeftShift;
    private $workerLeftShift;

    // 其他属性
    private $epoch;
    private $datacenterId;
    private $workerId;
    private $sequence = 0;
    private $lastTimestamp = -1;

    public function __construct($datacenterId = 0, $workerId = 0, $config = [])
    {
        // 19位ID的默认配置
        $defaultConfig = [
            'timestamp_bits' => 43,  // 43位时间戳
            'datacenter_bits' => 4,  // 4位数据中心
            'worker_bits' => 4,      // 4位机器
            'sequence_bits' => 12,   // 12位序列号
            'epoch' => 1640995200000 // 2022-01-01 00:00:00
        ];

        // 合并配置
        $config = array_merge($defaultConfig, $config);

        // 设置位数配置
        $this->timestampBits = $config['timestamp_bits'];
        $this->datacenterBits = $config['datacenter_bits'];
        $this->workerBits = $config['worker_bits'];
        $this->sequenceBits = $config['sequence_bits'];
        $this->epoch = $config['epoch'];

        // 验证位数总和
        $totalBits = $this->timestampBits + $this->datacenterBits + $this->workerBits + $this->sequenceBits;
        if ($totalBits > 63) { // 64位整数，1位符号位
            getResponseException("Total bits cannot exceed 63", [], 1, 'ERROR');
        }

        // 计算相关常量
        $this->calculateConstants();

        // 验证数据中心ID和工作节点ID
        if ($datacenterId > $this->maxDatacenterId || $datacenterId < 0) {
            getResponseException("Datacenter ID must be between 0 and " . $this->maxDatacenterId, [], 1, 'ERROR');
        }

        if ($workerId > $this->maxWorkerId || $workerId < 0) {
            getResponseException("Worker ID must be between 0 and " . $this->maxWorkerId, [], 1, 'ERROR');
        }

        $this->datacenterId = $datacenterId;
        $this->workerId = $workerId;
    }

    /**
     * 计算相关常量
     */
    private function calculateConstants()
    {
        $this->maxDatacenterId = -1 ^ (-1 << $this->datacenterBits);
        $this->maxWorkerId = -1 ^ (-1 << $this->workerBits);
        $this->maxSequence = -1 ^ (-1 << $this->sequenceBits);

        $this->timestampLeftShift = $this->datacenterBits + $this->workerBits + $this->sequenceBits;
        $this->datacenterLeftShift = $this->workerBits + $this->sequenceBits;
        $this->workerLeftShift = $this->sequenceBits;
    }

    /**
     * 生成雪花ID
     * @return int
     * @throws Exception
     */
    public function nextId()
    {
        $timestamp = $this->getTimestamp();

        if ($timestamp < $this->lastTimestamp) {
            $milliseconds = $this->lastTimestamp - $timestamp;
            throw new Exception("Clock moved backwards. Refusing to generate ID for {$milliseconds} milliseconds.");
        }

        if ($timestamp == $this->lastTimestamp) {
            $this->sequence = ($this->sequence + 1) & $this->maxSequence;

            if ($this->sequence == 0) {
                $timestamp = $this->waitNextMillis($timestamp);
            }
        } else {
            $this->sequence = 0;
        }

        $this->lastTimestamp = $timestamp;

        $id = (($timestamp - $this->epoch) << $this->timestampLeftShift) |
            ($this->datacenterId << $this->datacenterLeftShift) |
            ($this->workerId << $this->workerLeftShift) |
            $this->sequence;

        return $id;
    }

    /**
     * 等待下一毫秒
     * @param $lastTimestamp
     * @return mixed
     */
    private function waitNextMillis($lastTimestamp)
    {
        $timestamp = $this->getTimestamp();
        while ($timestamp <= $lastTimestamp) {
            $timestamp = $this->getTimestamp();
        }
        return $timestamp;
    }

    /**
     * 获取当前时间戳（毫秒）
     * @return float
     */
    private function getTimestamp()
    {
        return floor(microtime(true) * 1000);
    }

    /**
     * 解析雪花ID
     * @param $id
     * @return array
     */
    public function parseId($id)
    {
        $binary = str_pad(decbin($id), 64, '0', STR_PAD_LEFT);

        $timestamp = bindec(substr($binary, 0, $this->timestampBits)) + $this->epoch;
        $datacenterId = bindec(substr($binary, $this->timestampBits, $this->datacenterBits));
        $workerId = bindec(substr($binary, $this->timestampBits + $this->datacenterBits, $this->workerBits));
        $sequence = bindec(substr($binary, $this->timestampBits + $this->datacenterBits + $this->workerBits, $this->sequenceBits));

        return [
            'timestamp' => $timestamp,
            'datacenterId' => $datacenterId,
            'workerId' => $workerId,
            'sequence' => $sequence,
            'datetime' => date('Y-m-d H:i:s', intval($timestamp / 1000)),
            'id_length' => strlen((string)$id)
        ];
    }

    /**
     * 获取当前配置信息
     * @return array
     */
    public function getConfig()
    {
        return [
            'timestamp_bits' => $this->timestampBits,
            'datacenter_bits' => $this->datacenterBits,
            'worker_bits' => $this->workerBits,
            'sequence_bits' => $this->sequenceBits,
            'epoch' => $this->epoch,
            'max_datacenter_id' => $this->maxDatacenterId,
            'max_worker_id' => $this->maxWorkerId,
            'max_sequence' => $this->maxSequence,
            'total_bits' => $this->timestampBits + $this->datacenterBits + $this->workerBits + $this->sequenceBits
        ];
    }

}
